
use std::{collections::HashMap, convert::Infallible, net::SocketAddr, str::FromStr, sync::Arc};

use headers::{authorization::Basic, Authorization, HeaderMapExt};

use dav_server::{body::Body, memls::MemLs, DavHandler};
use hyper::{body::Incoming, server::conn::http1, service::service_fn, Request};
use hyper_util::rt::TokioIo;
use wtask_app::{cli_app, msg_func_none, AppHome};
use wtask_base::{
    error::WResult,
    export::{KillReceiver, TokioRwLock, log_debug, log_error, log_info, select, yield_now},
    machine::{CMDArgsConfigAll, CMDArgsConfigMachine, WSpawn, get_config, machine_port},
    tcp::tcp_listen
};

use super::fs::MachineFs;

// machine_config / home
pub(super) type MachineDavCre = (CMDArgsConfigMachine, String);
type MachineAll = Arc<TokioRwLock<HashMap<CMDArgsConfigMachine, String>>>;


pub struct MachineWebDAVServer {
    dh: DavHandler<MachineDavCre>,
    config: MachineAll,
    port: u32,
}

impl MachineWebDAVServer {
    pub fn new(port: u32) -> Self {
        let dh = DavHandler::builder()
            .filesystem(MachineFs::new())
            .locksystem(MemLs::new())
            .build_handler();
        let config = Arc::new(TokioRwLock::new(HashMap::new()));
        Self {
            dh,
            config,
            port,
        }
    }

    pub async fn listen(self, mut kill_rx_parent: KillReceiver<bool>) -> WResult<()> {
        let port = self.port;
        log_debug!("Start machine webdav ...", );
        let listener = tcp_listen(port, "MachineWebDav")?;
        log_debug!("Machine WebDav started");
        let mut wspawn = WSpawn::default();
        let (kill_tx, mut kill_rx) = wspawn.catch_pause_signal(async move {});
        loop {
            select! {
                Ok((stream, _)) = listener.accept() => {
                    let dh = self.dh.clone();
                    let config = self.config.clone();
                    let io = TokioIo::new(stream);
                    wspawn.spawn(async move {
                        let service = service_fn(|req: Request<Incoming>| {
                            let dh = dh.clone();
                            let config = config.clone();
                            async move { Self::handle(dh, config, req).await }
                        });
                        if let Err(e) = http1::Builder::new().serve_connection(io, service).await {
                            log_error!("webdav error: {e}");
                        }
                    });
                }
                _ = kill_rx.changed() => {
                    log_info!("Shutting Machine WebDav ...");
                    break;
                }
                _ = kill_rx_parent.changed() => {
                    log_info!("Shutting Machine WebDav, kill by parent ...");
                    let _ = kill_tx.send(true);
                    break;
                }
            }
            yield_now().await;
        }
        Ok(())
    }

    fn auth_error(msg: &str) -> Result<hyper::Response<Body>, Infallible> {
        let response = hyper::Response::builder()
            .status(401)
            .header("WWW-Authenticate", "Basic realm=\"wTask\"")
            .body(Body::from(msg.to_owned()))
            .unwrap();
        Ok(response)
    }

    pub(super) async fn handle(
        dh: DavHandler<MachineDavCre>,
        config_all: MachineAll,
        req: hyper::Request<hyper::body::Incoming>,
    ) -> Result<hyper::Response<Body>, Infallible> {
        let config = {
            match req.headers().typed_get::<Authorization<Basic>>() {
                Some(Authorization(basic)) => {
                    log_debug!("auth: {} {}", basic.username(), basic.password());
                    // auth
                    let addr = SocketAddr::from_str(&basic.username().replacen("@", ":", 1));
                    if addr.is_err() {
                        return Self::auth_error("username parse error");
                    }
                    let addr = addr.unwrap();
                    let config = CMDArgsConfigAll::Machine(CMDArgsConfigMachine {
                        machine_ip: addr.ip().to_string(),
                        machine_port: machine_port(addr.port() as u32, false),
                        proxy: false,
                        machine_token: basic.password().to_owned(),
                    });
                    let config = get_config(config).unwrap().pop().unwrap();
                    let home_already = config_all.read().await.get(&config).cloned();
                    let home = if let Some(home) = home_already {
                        home
                    } else {
                        log_info!("req login ...");
                        let home: String = match cli_app(AppHome, &config, None, msg_func_none).await {
                            Err(e) => {
                                log_debug!("login error: {e}");
                                return Self::auth_error("login error");
                            },
                            Ok(Some(p)) => {
                                log_info!("login success: {}", p);
                                p.into()
                            },
                            _ => {
                                log_debug!("login error: no res");
                                return Self::auth_error("login error");
                            },
                        };
                        config_all.write().await.insert(config.clone(), home.clone());
                        home
                    };
                    (config, home)
                },
                _ => {
                    log_debug!("auth error: {:?}", req.headers());
                    return Self::auth_error("No auth info");
                }
            }
        };
        Ok(dh.handle_guarded(req, config).await)
    }
}
